﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq.Expressions;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using MVVMFramework.Ria.Helpers;
using MVVMFramework.Core.Model;

namespace MVVMFramework.Ria.Model
{
    public class RiaQuery<T> : IDomainQuery<T>
        where T : Entity
    {
        private bool disposed;

        protected DomainContext Context { get; private set; }
        public bool IsLoading { get; protected set; }
        protected Func<EntityQuery<T>> CreateQuery { get; private set; }

        protected LoadOperation<T> CurrentLoadOperation { get; set; }

        public RiaQuery(DomainContext ctx, Func<EntityQuery<T>> createQuery)
        {
            Context = ctx;
            CreateQuery = createQuery;
        }

        #region IDomainQuery<T> Members

        public Func<Expression<Func<T, bool>>> FilterExpression { get; set; }
        public Func<SortDescriptionCollection> SortDescriptions { get; set; }
        public Func<PageDescription> PageDescription { get; set; }

        public event Action Canceled;
        public event Action Loading;
        public event Action<ILoadOperation<T>> Loaded;
        event Action<ILoadOperation> IDomainQuery.Loaded
        {
            add
            {
                this.Loaded += q => value(q);
            }
            remove
            {
                this.Loaded -= q => value(q);
            }
        }

        /// <summary>
        /// This would be the place to cache page results and avoid "unecessary" server roundtrips. 
        /// The current implementation always does a server roundtrip.
        /// </summary>
        public virtual void Load()
        {
            if (IsLoading)
                return;

            var loadingEvt = this.Loading;
            if (loadingEvt != null)
                loadingEvt();

            var query = CreateQuery(); //create query

            if (this.FilterExpression != null)
                query = query.Where(this.FilterExpression()); //apply filters

            if (this.SortDescriptions != null)
                query = ApplySortDescriptions(query); //apply sorting
            if (this.PageDescription != null)
            {
                query.IncludeTotalCount = true;
                query = ApplyPageDescription(query, this.PageDescription()); //apply paging
            }

            this.CurrentLoadOperation = Context.Load(query, OnLoaded, null);
            IsLoading = true;
        }

        public virtual void Cancel()
        {
            if (this.CurrentLoadOperation != null
                && !this.CurrentLoadOperation.IsComplete
                && this.CurrentLoadOperation.CanCancel)
            {
                this.CurrentLoadOperation.Cancel();
                this.IsLoading = false;
                var ev = this.Canceled;
                if (ev != null)
                    ev();
            }
        }

        #endregion

        protected virtual void OnLoaded(LoadOperation<T> op)
        {
            IsLoading = false;

            if (!op.HasError)
            {
                if (!op.IsCanceled)
                {
                    var loadedEvt = this.Loaded;
                    if (loadedEvt != null)
                        loadedEvt(new RiaLoadOperation<T>(op));
                }
            }
            else
            {
                if (!this.disposed)
                {
                    //Todo: Error handling
                    //MessageBox.Show(op.Error.Message);
                }
                op.MarkErrorAsHandled();
            }

            this.CurrentLoadOperation = null;
        }

        private EntityQuery<T> ApplySortDescriptions(EntityQuery<T> query)
        {
            var sortDescriptions = this.SortDescriptions != null ? this.SortDescriptions() : null;
            if (sortDescriptions != null && sortDescriptions.Count > 0)
                return EntityQueryHelper<T>.BuildSortQueryForSortDescriptions(query, sortDescriptions);

            return EntityQueryHelper<T>.BuildSortQueryForEntityKeys(query);
        }

        private EntityQuery<T> ApplyPageDescription(EntityQuery<T> query, PageDescription description)
        {
            return query.Skip(description.PageIndex * description.PageSize).Take(description.PageSize);
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            if (this.IsLoading)
                this.Cancel();

            this.disposed = false;
            this.Loaded = null;
            this.Loading = null;
        }

        #endregion
    }
}
